/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package tools;

import com.openbrew.openbrewrf.ACB;
import com.openbrew.openbrewrf.ACT;
import com.openbrew.openbrewrf.ATBeer;
import com.openbrew.openbrewrf.ATCust;
import com.openbrew.openbrewrf.Allocation;
import com.openbrew.openbrewrf.AllocationTemplate;
import com.openbrew.openbrewrf.Customer;
import com.openbrew.openbrewrf.CustomerGroup;
import com.openbrew.openbrewrf.DiscountCode;
import com.openbrew.openbrewrf.PSOLines;
import com.openbrew.openbrewrf.PendingSO;
import com.openbrew.openbrewrf.views.FGIView;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import model.RepetitionType;

/**
 *
 * @author chapmand
 */
public class AllocationTools {
    private final int MONTHS_TO_REPEAT = 4;
    
    /***************************************************************************
     * createPSOFromAllocation - Create a Pending SO from Allocation.
     * @param en
     * @param al
     * @param custID 
     **************************************************************************/
    public void createPSOFromAllocation(final EntityManager en, final Allocation al, final int custID){
        final Customer cust = getCustomerFromID(en, custID);
        PendingSOTools psoTools = new PendingSOTools();
        if(cust.getCustHold()==0){
            if(!getCustomerFilled(al, cust)){
                ACT act = getCustACT(al, cust);
                if(act.getACBCollection()!=null){
                    final Date saleDate = al.getAllocationDueDate();
                    List<PSOLines> psols = createPendingSOLines(en, act);
                    PendingSO pso = new PendingSO();
                    pso.setPSOAllocationID(al.getAllocationID());
                    pso.setPSOComplete(false);
                    pso.setVersion(0);
                    pso.setPSOCustID(cust);
                    pso.setPSODate(saleDate);
                    pso.setPSOLinesCollection(psols);
                    pso.setPsoxml("");
                    psoTools.createPendingSO(en, pso);
                    
                    //Update acts to show they were sold.
                    updateACTSold(en, act);
                    
                    if(checkAllocationFilled(al)){
                        Allocation alloc = getAllocationFromID(en, al.getAllocationID());
                        alloc.setAllocationFilled(true);
                        en.merge(alloc);
                    }
                }
            } 
        } 
    }
    
    private boolean checkAllocationFilled(final Allocation al){
        boolean retVal = true;
        if(al.getACTCollection()!=null){
            for(final ACT act : al.getACTCollection()){
                if(act.getACBCollection()!=null){
                    for(final ACB acb : act.getACBCollection()){
                        if(!acb.getACBFilled()){
                            retVal = false;
                        }
                    }
                }
            }
        }
        return retVal;
    }
    
    private void updateACTSold(final EntityManager en, final ACT act){
        for(final ACB acb : act.getACBCollection()){
            acb.setACBFilled(true);
            en.merge(acb);
        }
        en.getEntityManagerFactory().getCache().evictAll();
    }
        
    private List<PSOLines> createPendingSOLines(final EntityManager en, final ACT act){
        List<PSOLines> psols = new ArrayList<PSOLines>();
        for(final ACB acb : act.getACBCollection()){
                FGIView fgi = acb.getACBFGI();
                DiscountCode disc = getDiscountCodeFromID(en, 1);
                if(act.getACTCustomer().getDiscountCode()!=null){
                    disc = act.getACTCustomer().getDiscountCode();
                }
                if(fgi.getFGIPackageID().getPackageTypeVol()>0.15){
                    for(int i=0; i<acb.getACBQty(); i++){
                        PSOLines psoLine = new PSOLines();
                        psoLine.setPSOFGI(fgi);
                        psoLine.setPSODiscCode(disc);
                        psoLine.setPSOKeg(0);
                        psoLine.setPSOPriceOverRide(0);
                        psoLine.setPSOQty(1);
                        psoLine.setPSOQtyLeft(1);
                        psoLine.setPSOSold(false);
                        psols.add(psoLine);
                    }
                } else {
                    PSOLines psoLine = new PSOLines();
                    psoLine.setPSOFGI(fgi);
                    psoLine.setPSODiscCode(disc);
                    psoLine.setPSOKeg(0);
                    psoLine.setPSOPriceOverRide(0);
                    psoLine.setPSOQty(acb.getACBQty());
                    psoLine.setPSOQtyLeft(acb.getACBQty());
                    psoLine.setPSOSold(false);
                    psols.add(psoLine);
                }
        }
        return psols;
    }
    
    private boolean getCustomerFilled(final Allocation al, final Customer cust){
        final ACT act = getCustACT(al, cust);
        boolean retVal = true;
        if(act.getACBCollection()!=null){
            for(final ACB acb : act.getACBCollection()){
                if(!acb.getACBFilled()){
                    retVal = false;
                }
            }
        }
        return retVal;
    }
    
    private ACT getCustACT(final Allocation al, final Customer cust){
        final long custID = cust.getCId();
        if(al.getACTCollection()!=null){
            for(final ACT act : al.getACTCollection()){
                final long actCustID = act.getACTCustomer().getCId();
                if(actCustID == custID){
                    return act;
                }
            }
        }
        return null;
    }
    
    /***************************************************************************
     * Create a new template from allocation.
     * @param en
     * @param description
     * @param al
     * @param repType
     * @param repCount
     * @return 
     **************************************************************************/
    public AllocationTemplate createNewTemplateFromAllocation(final EntityManager en, final String description
            , final Allocation al, final int repType, final int repCount){
        AllocationTemplate newAT = new AllocationTemplate();
        newAT.setATCGroup(al.getAllocationCustGroupID().getCustomerGroupID());
        newAT.setATDesc(description);
        newAT.setRepCount(repCount);
        newAT.setRepType(repType);
        
        List<ATCust> newCustTemplates = new ArrayList<ATCust>();
        if(al.getACTCollection()!=null){
            for(final ACT act : al.getACTCollection()){
                ATCust newCust = new ATCust();
                newCust.setAtCustomer(act.getACTCustomer());
                List<ATBeer> newBeers = new ArrayList<ATBeer>();
                for(final ACB acb : act.getACBCollection()){
                    ATBeer newBeer = new ATBeer();
                    newBeer.setAtFgiID(acb.getACBFGI());
                    newBeer.setATBeerQty(acb.getACBQty());
                    newBeer.setServingTankVolume(acb.getServingTankVolume());
                    en.persist(newBeer);
                    newBeers.add(newBeer);
                }
                newCust.setAtBeerCollection(newBeers);
                en.persist(newCust);
                newCustTemplates.add(newCust);
            }
        }
        newAT.setAtCustCollection(newCustTemplates);
        
        en.persist(newAT);
        return newAT;
    }
    
    /***************************************************************************
     * Update template from allocation
     * @param en
     * @param templateID
     * @param al 
     **************************************************************************/
    public void updateTemplateFromAllocation(final EntityManager en, final int templateID, final Allocation al){
        AllocationTemplate at = getAllocationTemplateFromID(en, templateID);
        
        for(ATCust atc : at.getAtCustCollection()){
            en.remove(en.merge(atc));
            en.getEntityManagerFactory().getCache().evictAll();
        }
        
        List<ATCust> newCustTemplates = new ArrayList<ATCust>();
        if(al.getACTCollection()!=null){
            for(final ACT act : al.getACTCollection()){
                ATCust newCust = new ATCust();
                newCust.setAtCustomer(act.getACTCustomer());
                List<ATBeer> newBeers = new ArrayList<ATBeer>();
                for(final ACB acb : act.getACBCollection()){
                    ATBeer newBeer = new ATBeer();
                    newBeer.setAtFgiID(acb.getACBFGI());
                    newBeer.setATBeerQty(acb.getACBQty());
                    newBeer.setServingTankVolume(acb.getServingTankVolume());
                    en.persist(newBeer);
                    newBeers.add(newBeer);
                }
                newCust.setAtBeerCollection(newBeers);
                en.persist(newCust);
                newCustTemplates.add(newCust);
            }
        }
        at.setAtCustCollection(newCustTemplates);
        en.merge(at);
    }
    
    /***************************************************************************
     * Remove All similar allocations from given date on.
     * @param en
     * @param date
     * @param allocationID 
     **************************************************************************/
    public void removeAllSimilarAllocationsFromDate(final EntityManager en, final Date date, final int allocationID){
        List<Allocation> allocations = getOpenAllocations(en);
        for(Allocation allocation : allocations){
            if(allocation.getTemplateID() == allocationID){
                if(allocation.getAllocationDueDate().after(date) || allocation.getAllocationDueDate().equals(date)){
                    en.remove(en.merge(allocation));
                    en.getEntityManagerFactory().getCache().evictAll();
                }
            }
        }
    }

    /***************************************************************************
     * Code to update similar allocations
     *
     * @param en
     * @param newAlloc
     * @param daysChanged 
     * @return  List of all allocations that have been updated.
     **************************************************************************/
    public List<Allocation> updateAllSimilarAllocations(final EntityManager en, final Allocation newAlloc, final int daysChanged) {
        List<Allocation> modAllocs = new ArrayList<Allocation>();
        modAllocs.add(newAlloc);
        if (newAlloc.getTemplateID() != 0) {
            final AllocationTemplate aTemplate = getAllocationTemplateFromID(en, newAlloc.getTemplateID());
            final int allocationID = newAlloc.getAllocationID();
            final int templateID = newAlloc.getTemplateID();
            List<Allocation> allocations = getOpenAllocations(en);
            if (checkForRepetition(aTemplate) && (newAlloc.getACTCollection()!=null)) {
                final List<ACT> acts = newAlloc.getACTCollection();
                for (Allocation modAlloc : allocations) {
                    if (!newAlloc.getAllocationFilled() && (templateID == modAlloc.getTemplateID())
                            && (allocationID != modAlloc.getAllocationID())) {
                        if (daysChanged != 0) {
                            //days have changed, update accordingly.
                            Calendar cal = Calendar.getInstance();
                            cal.setTime(modAlloc.getAllocationDueDate());
                            cal.add(Calendar.DAY_OF_YEAR, daysChanged);
                            modAlloc.setAllocationDueDate(cal.getTime());
                        }
                        
                        for(final ACT newACT : acts){
                            final long customerID = newACT.getACTCustomer().getCId();
                            final Customer cust = newACT.getACTCustomer();
                            final List<ACB> acbs = newACT.getACBCollection();
                            boolean custFound = false;
                            for(ACT modACT : modAlloc.getACTCollection()){
                                if(modACT.getACTCustomer().getCId() == customerID){
                                    custFound = true;
                                    for(final ACB newACB : acbs){
                                        final int beerID = newACB.getACBFGI().getFgiid();
                                        final FGIView fgi = newACB.getACBFGI();
                                        final int qty = newACB.getACBQty();
                                        final float volume = newACB.getServingTankVolume();
                                        boolean beerFound = false;
                                        for(ACB modACB : modACT.getACBCollection()){
                                            if(modACB.getACBFGI().getFgiid() == beerID){
                                                modACB.setACBQty(qty);
                                                modACB.setServingTankVolume(volume);
                                                beerFound = true;
                                            }
                                        }
                                        if(!beerFound){
                                            ACB acb = new ACB();
                                            acb.setACBFGI(fgi);
                                            acb.setACBFilled(false);
                                            acb.setACBQty(qty);
                                            acb.setServingTankVolume(volume);
                                            en.persist(acb);
                                            modACT.getACBCollection().add(acb);
                                        }
                                    }
                                }
                            }
                            if(!custFound){
                                ACT act = new ACT();
                                act.setACTCustomer(cust);
                                List<ACB> beers = new ArrayList<ACB>();
                                for(ACB newACB : newACT.getACBCollection()){
                                    ACB acb = new ACB();
                                    acb.setACBFGI(newACB.getACBFGI());
                                    acb.setACBFilled(false);
                                    acb.setACBQty(newACB.getACBQty());
                                    acb.setServingTankVolume(newACB.getServingTankVolume());
                                    en.persist(acb);
                                    beers.add(acb);
                                }
                                act.setACBCollection(beers);
                                en.persist(act);
                                modAlloc.getACTCollection().add(act);
                            }
                        }
                        
                        //Update the allocation.
                        en.merge(modAlloc);
                        modAllocs.add(modAlloc);
                    }
                }
            }
        }
        return modAllocs;
    }

    /**
     * *************************************************************************
     * Code to check validity and insert allocations if valid.
     *
     * @param en
     * @param templateID
     * @param dueDate
     * @return 
     *************************************************************************
     */
    public List<Allocation> checkAndInsertAllocation(final EntityManager en, final int templateID, final Date dueDate) {
        List<Allocation> allocs = new ArrayList<Allocation>();
        final Date today = new Date();
        final AllocationTemplate at = getAllocationTemplateFromID(en, templateID);
        Calendar c = Calendar.getInstance();
        c.setTime(dueDate);
        int dueWeek = c.get(Calendar.WEEK_OF_YEAR);
        int dueYear = c.get(Calendar.YEAR);
        boolean runAuto = true;

        List<Allocation> allocations = getOpenAllocations(en);

        for (final Allocation al : allocations) {
            c.setTime(al.getAllocationDueDate());
            int week = c.get(Calendar.WEEK_OF_YEAR);
            int year = c.get(Calendar.YEAR);
            if ((week == dueWeek) && (year == dueYear) && (al.getAllocationCustGroupID().getCustomerGroupID()
                    == at.getATCGroup())) {
                runAuto = false;
            }
        }
        if (runAuto == true) {
            //Call function to create a automated forcast.
            allocs.add(insertAllocationFromTemplate(en, at, dueDate, today));
            List<Allocation> als = autoInsertAllocationTemplates(en, at, allocs.get(0), allocations);
            if (!als.isEmpty()) {
                for (final Allocation al : als) {
                    allocs.add(al);
                }
            }
            return allocs;
        } else {
            return null;
        }
    }

    private Allocation insertAllocationFromTemplate(final EntityManager en, final AllocationTemplate at, final Date dueDate, final Date today) {
        final Allocation al = new Allocation();
        al.setAllocationDueDate(dueDate);
        al.setAllocationModDate(today);
        al.setAllocationStartDate(today);
        al.setAllocationFilled(false);
        al.setAllocationCustGroupID(getCustomerGroupViewFromID(en, at.getATCGroup()));
        al.setTemplateID(at.getAtid());
        if (at.getAtCustCollection() == null) {
            return null;
        }
        List<ACT> acts = new ArrayList<ACT>();
        for (final ATCust custTemplate : at.getAtCustCollection()) {
            List<ACB> acbs = new ArrayList<ACB>();
            for (final ATBeer beer : custTemplate.getAtBeerCollection()) {
                ACB newACB = new ACB();
                newACB.setACBFilled(false);
                newACB.setACBFGI(beer.getAtFgiID());
                newACB.setACBQty(beer.getATBeerQty());
                newACB.setServingTankVolume(beer.getServingTankVolume());
                acbs.add(newACB);
            }
            ACT newACT = new ACT();
            newACT.setACTCustomer(custTemplate.getAtCustomer());
            newACT.setACBCollection(acbs);
            acts.add(newACT);
        }
        al.setACTCollection(acts);
        en.persist(al);
        return al;
    }

    private List<Allocation> autoInsertAllocationTemplates(final EntityManager en, final AllocationTemplate at, final Allocation al, final List<Allocation> allocations) {
        List<Allocation> allocs = new ArrayList<Allocation>();
        if (checkForRepetition(at)) {
            //allocations should repeat
            RepetitionType rt = RepetitionType.getType(at.getRepType());
            int repeats = getNumberOfRepeats(en, al.getTemplateID(), allocations);
            //Get Todays Date
            Calendar ct = Calendar.getInstance();
            ct.setTime(new Date());
            ct.set(Calendar.HOUR, 0);
            ct.set(Calendar.MINUTE, 0);
            ct.set(Calendar.SECOND, 0);
            ct.set(Calendar.MILLISECOND, 0);
            final Date today = ct.getTime();
            //Set initial calendar time
            Calendar cal = Calendar.getInstance();
            cal.setTime(al.getAllocationDueDate());

            if (rt == RepetitionType.YEARLY && repeats < 1) {
                //add on a year out if not already existant
                cal.add(Calendar.YEAR, 1);
                final Date dueDate = cal.getTime();
                allocs.add(insertAllocationFromTemplate(en, at, dueDate, today));
            } else if (rt == RepetitionType.QUARTERLY) {
                //add on for 2 quarters if not already existant
                for (int x = 0; x < 2; x++) {
                    cal.add(Calendar.MONTH, 3);
                    if (x >= repeats) {
                        final Date dueDate = cal.getTime();
                        allocs.add(insertAllocationFromTemplate(en, at, dueDate, today));
                    }
                }
            } else {
                Date dueDate = cal.getTime();
                Calendar calStop = Calendar.getInstance();
                calStop.setTime(dueDate);
                calStop.add(Calendar.MONTH, MONTHS_TO_REPEAT);
                final Date stopDate = calStop.getTime();
                //int x = 0;
                while (dueDate.before(stopDate) || dueDate.equals(stopDate)) {
                    if (rt == RepetitionType.MONTHLY) {
                        cal.add(Calendar.MONTH, at.getRepCount());
                        if (!doesAllocationExistInMonth(cal, al.getTemplateID(), allocations)) {
                            dueDate = cal.getTime();
                            allocs.add(insertAllocationFromTemplate(en, at, dueDate, today));
                        }
                    } else if (rt == RepetitionType.WEEKLY) {
                        cal.add(Calendar.WEEK_OF_YEAR, at.getRepCount());
                        if (!doesAllocationExistOnWeek(cal, al.getTemplateID(), allocations)) {
                            dueDate = cal.getTime();
                            allocs.add(insertAllocationFromTemplate(en, at, dueDate, today));
                        }
                    } else {
                        cal.add(Calendar.DAY_OF_YEAR, at.getRepCount());
                        if (!doesAllocationExistOnDay(cal, al.getTemplateID(), allocations)) {
                            dueDate = cal.getTime();
                            allocs.add(insertAllocationFromTemplate(en, at, dueDate, today));
                        }
                    }
                }
            }
        }
        en.getEntityManagerFactory().getCache().evictAll();
        return allocs;
    }

    private boolean checkForRepetition(final AllocationTemplate at) {
        //Looks through all open templates and updates them if the template ids match.
        if (at != null) {
            if (at.getRepType() != RepetitionType.NONE.getType() && at.getRepCount() > 0) {
                return true;
            }
        }
        return false;
    }

    private int getNumberOfRepeats(final EntityManager en, final int templateID, final List<Allocation> allocations) {
        int num = 0;
        for (final Allocation al : allocations) {
            if (al.getTemplateID() == templateID) {
                if (!al.getAllocationFilled()) {
                    num++;
                }
            }
        }
        return num;
    }

    private boolean doesAllocationExistOnWeek(final Calendar calTest, final int templateID, final List<Allocation> allocations) {
        final int year = calTest.get(Calendar.YEAR);
        final int weekOfYear = calTest.get(Calendar.WEEK_OF_YEAR);
        for (final Allocation al : allocations) {
            if (al.getTemplateID() == templateID) {
                Calendar cal = Calendar.getInstance();
                cal.setTime(al.getAllocationDueDate());
                final int alYear = cal.get(Calendar.YEAR);
                final int alWeek = cal.get(Calendar.WEEK_OF_YEAR);
                if (alYear == year && alWeek == weekOfYear) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean doesAllocationExistInMonth(final Calendar calTest, final int templateID, final List<Allocation> allocations) {
        final int year = calTest.get(Calendar.YEAR);
        final int month = calTest.get(Calendar.MONTH);
        for (final Allocation al : allocations) {
            if (al.getTemplateID() == templateID) {
                Calendar cal = Calendar.getInstance();
                cal.setTime(al.getAllocationDueDate());
                final int alYear = cal.get(Calendar.YEAR);
                final int alMonth = cal.get(Calendar.MONTH);
                if (alYear == year && alMonth == month) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean doesAllocationExistOnDay(final Calendar calTest, final int templateID, final List<Allocation> allocations) {
        for (final Allocation al : allocations) {
            if (al.getTemplateID() == templateID) {
                if (al.getAllocationDueDate().equals(calTest.getTime())) {
                    return true;
                }
            }
        }
        return false;
    }

    private CustomerGroup getCustomerGroupViewFromID(final EntityManager en, final int custGroupID) {
        TypedQuery<CustomerGroup> query = en.createQuery(
                "SELECT c FROM CustomerGroup c WHERE c.customerGroupID = ?1", CustomerGroup.class);
        query.setParameter(1, custGroupID);
        return query.getSingleResult();
    }

    private Customer getCustomerFromID(final EntityManager en, final int custID) {
        TypedQuery<Customer> query = en.createQuery(
                "SELECT c FROM Customer c WHERE c.cId = ?1", Customer.class);
        query.setParameter(1, custID);
        return query.getSingleResult();
    }

    private DiscountCode getDiscountCodeFromID(final EntityManager en, final int discID) {
        TypedQuery<DiscountCode> query = en.createQuery(
                "SELECT d FROM DiscountCode d WHERE d.discID = ?1", DiscountCode.class);
        query.setParameter(1, discID);
        return query.getSingleResult();
    }

    private AllocationTemplate getAllocationTemplateFromID(final EntityManager en, final int templateID) {
        TypedQuery<AllocationTemplate> query = en.createQuery(
                "SELECT a FROM AllocationTemplate a WHERE a.atid = ?1", AllocationTemplate.class);
        query.setParameter(1, templateID);
        return query.getSingleResult();
    }

    private Allocation getAllocationFromID(final EntityManager en, final int allocID) {
        TypedQuery<Allocation> query = en.createQuery(
                "SELECT a FROM Allocation a WHERE a.allocationID = ?1", Allocation.class);
        query.setParameter(1, allocID);
        return query.getSingleResult();
    }

    private List<Allocation> getOpenAllocations(final EntityManager en) {
        TypedQuery<Allocation> query = en.createNamedQuery("Allocation.findOpenAllocations", Allocation.class);
        return query.getResultList();
    }
}
